﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using AForge.Imaging;
using AForge.Video;
using System.Runtime.InteropServices;
using Image = System.Drawing.Image;

namespace Damany.Cameras
{
    public class CyNetCamera : IVideoSource
    {
        private IPEndPoint _endpoint;
        private Thread _receiveThread;
        private Thread _workerThread;
        private TcpClient _tcpSocket;
        private CancellationTokenSource _tcs = new CancellationTokenSource();
        private ConcurrentQueue<byte[]> _frameQueue = new ConcurrentQueue<byte[]>();
        private const int MaxLen = 10 * 1024 * 1024;



        public CyNetCamera(System.Net.IPEndPoint endpoint)
        {
            _endpoint = endpoint;

        }

        public void Start()
        {
            if (_receiveThread == null)
            {
                _receiveThread = new Thread(ReceiveThread);
                _receiveThread.IsBackground = true;
                _receiveThread.Start();

                _workerThread = new Thread(WorkerThread);
                _workerThread.IsBackground = true;
                _workerThread.Start();
            }
        }

        public void SignalToStop()
        {
            _tcs.Cancel();
        }

        public void WaitForStop()
        {
            if (IsRunning)
            {

            }
        }

        public void Stop()
        {
            _tcs.Cancel();
        }

        public string Source { get; private set; }
        public int FramesReceived { get; private set; }
        public int BytesReceived { get; private set; }
        public bool IsRunning { get; private set; }
        public event NewFrameEventHandler NewFrame;

        protected virtual void OnNewFrame(NewFrameEventArgs eventargs)
        {
            NewFrameEventHandler handler = NewFrame;
            if (handler != null) handler(this, eventargs);
        }

        public event VideoSourceErrorEventHandler VideoSourceError;
        public event PlayingFinishedEventHandler PlayingFinished;

        private void WorkerThread(object state)
        {
            var timer = new System.Diagnostics.Stopwatch();
            while (!_tcs.Token.IsCancellationRequested)
            {
                byte[] frame;
                if (_frameQueue.TryDequeue(out frame))
                {
                    try
                    {
                        timer.Restart();
                        var img = Image.FromStream(new MemoryStream(frame));
                        OnNewFrame(new NewFrameEventArgs((Bitmap) img));
                        var elapsedTime = timer.ElapsedMilliseconds;
                        System.Diagnostics.Debug.WriteLine("Handle Frame Took {0} millisecond", elapsedTime);
                    }
                    catch (Exception ex)
                    {

                    }
                }
                else
                {
                    Thread.Sleep(500);
                }
            }

        }

        private void ReceiveThread(object state)
        {
            while (!_tcs.Token.IsCancellationRequested)
            {
                try
                {
                    _tcpSocket = new TcpClient();
                    _tcpSocket.Connect(_endpoint);

                    var headerLen = Marshal.SizeOf(typeof(Header));
                    while (!_tcs.Token.IsCancellationRequested)
                    {
                        var headerBuffer = new byte[headerLen];
                        var bytesRead = ReadLength(headerBuffer, 0, headerBuffer.Length, _tcpSocket.GetStream());
                        if (bytesRead != headerBuffer.Length)
                        {
                            CloseSocket();
                            break;
                        }
                        var header = BytesToHeader(headerLen, headerBuffer);

                        DateTime time;

                        try
                        {
                            time = new DateTime(
                                header.year,
                                header.month,
                                header.day,
                                header.hour,
                                header.minute,
                                header.second,
                                (int)header.msecond / 1000);
                        }
                        catch (ArgumentOutOfRangeException)
                        {
                            System.Diagnostics.Debug.WriteLine("DateTime out of range!!!");
                            continue;
                        }


                        var jpegCount = header.img_num;
                        var capturedImages = new List<byte[]>(jpegCount);
                        if (jpegCount > 0)
                        {
                            var jpegLen = header.jpeg_len;
                           // System.Diagnostics.Debug.WriteLine("len={0}, jpegLen={1}, h264len={2}" + DateTime.Now, header.len, header.jpeg_len, header.h264_len);
                            if (jpegLen > MaxLen)
                            {
                                CloseSocket();
                                break;
                            }
                            var img = new byte[jpegLen];
                            var jpegRead = ReadLength(img, 0, img.Length, _tcpSocket.GetStream());
                            if (jpegRead != img.Length)
                            {
                                CloseSocket();
                                break;
                            }


                            capturedImages.Add(img);
                        }

                        if (jpegCount > 1)
                        {
                            var jpegLen = header.jpeg_len2;
                            //System.Diagnostics.Debug.WriteLine("len={0}, jpegLen={1}, h264len={2}" + DateTime.Now, header.len, header.jpeg_len, header.h264_len);
                            if (jpegLen > MaxLen)
                            {
                                CloseSocket();
                                break;
                            }
                            var img = new byte[jpegLen];
                            var jpegRead = ReadLength(img, 0, img.Length, _tcpSocket.GetStream());
                            if (jpegRead != img.Length)
                            {
                                CloseSocket();
                                break;
                            }
                            var time2 = new DateTime(
                                header.year2,
                                header.month2,
                                header.day2,
                                header.hour2,
                                header.minute2,
                                header.second2,
                                (int)header.msecond2 / 1000);

                            capturedImages.Add(img);
                        }

                        var h264Len = header.h264_len;
                        if (h264Len > MaxLen)
                        {
                            CloseSocket();
                            break;
                        }
                        var h264 = new byte[h264Len];
                        var h264Read = ReadLength(h264, 0, h264.Length, _tcpSocket.GetStream());
                        if (h264Read != h264Len)
                        {
                            CloseSocket();
                            break;
                        }

                        foreach (var capturedImage in capturedImages)
                        {
                            _frameQueue.Enqueue(capturedImage);
                        }

                    }
                }
                catch (Exception ex)
                {
                    System.Diagnostics.Debug.Write(ex.Message);
                    CloseSocket();
                }
            }

            System.Diagnostics.Debug.WriteLine("end of receiving");
        }

        private static int ReadLength(byte[] buffer, int offset, int size, Stream stream)
        {
            var count = 0;
            var bytesLeftToRead = size - count;
            while (bytesLeftToRead > 0)
            {
                var bytesRead = stream.Read(buffer, offset + count, bytesLeftToRead);
                count += bytesRead;
                if (bytesRead == 0)
                {
                    break;
                }

                bytesLeftToRead -= bytesRead;
            }

            return count;
        }



        private void CloseSocket()
        {
            if (_tcpSocket != null && _tcpSocket.Connected)
            {
                _tcpSocket.Close();
                _tcpSocket = null;
            }
        }

        private static Header BytesToHeader(int headerLen, byte[] headerBuffer)
        {
            IntPtr ptr = Marshal.AllocHGlobal(headerLen);
            Marshal.Copy(headerBuffer, 0, ptr, headerLen);
            var header = (Header)Marshal.PtrToStructure(ptr, typeof(Header));
            Marshal.FreeHGlobal(ptr);
            return header;
        }


        [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        struct Header
        {

            /// ushort->unsigned short
            public ushort width;

            /// ushort->unsigned short
            public ushort height;

            /// ushort->unsigned short
            public ushort bits;

            /// ushort->unsigned short
            public ushort format;

            /// ushort->unsigned short
            public ushort frame_type;

            /// ushort->unsigned short
            public ushort frame_rev;

            /// ulong->unsigned int
            public uint firmware_version;

            /// ulong->unsigned int
            public uint device_no; //设备id

            /// ulong->unsigned int
            public uint len;

            /// ulong->unsigned int
            public uint speed;

            /// ulong->unsigned int
            public uint rs232;

            /// ushort->unsigned short
            public ushort year;

            /// ushort->unsigned short
            public ushort month;

            /// ushort->unsigned short
            public ushort day;

            /// ushort->unsigned short
            public ushort hour;

            /// ushort->unsigned short
            public ushort minute;

            /// ushort->unsigned short
            public ushort second;

            /// ulong->unsigned int
            public uint ip;

            /// ulong->unsigned int
            public uint frame_count;

            /// ulong->unsigned int
            public uint trigger_count;

            /// ulong->unsigned int
            public uint trigger_index;

            /// ulong->unsigned int
            public uint frame_no;

            /// ulong->unsigned int
            public uint gain;

            /// ulong->unsigned int
            public uint time;

            /// ulong->unsigned int
            public uint gain_r;

            /// ulong->unsigned int
            public uint gain_g;

            /// ulong->unsigned int
            public uint gain_b;

            /// ulong->unsigned int
            public uint mode;

            /// ulong->unsigned int
            public uint JpegQ;

            /// ushort->unsigned short
            public ushort year2;

            /// ushort->unsigned short
            public ushort month2;

            /// ushort->unsigned short
            public ushort day2;

            /// ushort->unsigned short
            public ushort hour2;

            /// ulong->unsigned int
            public uint trig_chl;

            /// ulong->unsigned int
            public uint msecond;

            /// ulong->unsigned int
            public uint msecond2;

            /// ulong->unsigned int
            public uint mpeg_head;

            /// ushort->unsigned short
            public ushort img_num;   //图片数目

            /// ushort->unsigned short
            public ushort plate_num; //车牌数目

            /// ushort->unsigned short
            public ushort h264width;

            /// ushort->unsigned short
            public ushort h264height;

            /// ulong->unsigned int
            public uint h264_len; //视频长度

            /// ushort->unsigned short
            public ushort minute2;

            /// ushort->unsigned short
            public ushort second2;

            /// ushort[3]
            [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 2, ArraySubType = UnmanagedType.U2)]
            public ushort[] rev;

            public ushort speed_limit; //限速值

            /// ushort->unsigned short
            public ushort vehicle_speed; //车速

            /// ulong->unsigned int
            public uint jpeg_len2;

            /// ulong->unsigned int
            public uint jpeg_len;

            /// unchar[32]
            [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 32)]
            public string user_info;

            /// char[16]
            [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 16)]
            public string plate1;

            /// char[16]
            [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 16)]
            public string plate2;
        }



    }




}
